import os
from google.appengine.ext.db import *

def monkey_patch(name, bases, namespace):
    assert len(bases) == 1, 'Exactly one base class is required'
    base = bases[0]
    for name, value in namespace.iteritems():
        if name not in ('__metaclass__', '__module__'):
            setattr(base, name, value)
    return base


class Model(Model):
    def __init__(self,
               parent=None,
               key_name=None,
               _app=None,
               _from_entity=False,
               **kwds):
        namespace = None
        if isinstance(_app, tuple):
            if len(_app) != 2:
                raise BadArgumentError('_app must have 2 values if type is tuple.')
            _app, namespace = _app
        key = kwds.get('key', None)
        if key is not None:
            if isinstance(key, (tuple, list)):
                key = Key.from_path(*key)
            if isinstance(key, basestring):
                key = Key(encoded=key)
            if not isinstance(key, Key):
                raise TypeError('Expected Key type; received %s (is %s)' %
                                (key, key.__class__.__name__))
            if not key.has_id_or_name():
                raise BadKeyError('Key must have an id or name')
            if key.kind() != self.kind():
                raise BadKeyError('Expected Key kind to be %s; received %s' %
                                  (self.kind(), key.kind()))
            if _app is not None and key.app() != _app:
                raise BadKeyError('Expected Key app to be %s; received %s' %
                                  (_app, key.app()))
            if namespace is not None and key.namespace() != namespace:
                raise BadKeyError('Expected Key namespace to be %s; received %s' %
                                  (namespace, key.namespace()))
            if key_name and key_name != key.name():
                raise BadArgumentError('Cannot use key and key_name at the same time'
                                       ' with different values')
            if parent and parent != key.parent():
                raise BadArgumentError('Cannot use key and parent at the same time'
                                       ' with different values')
            namespace = key.namespace()
            self._key = key
            self._key_name = None
            self._parent = None
            self._parent_key = None
        else:
            if key_name == '':
                raise BadKeyError('Name cannot be empty.')
            elif key_name is not None and not isinstance(key_name, basestring):
                raise BadKeyError('Name must be string type, not %s' %
                                  key_name.__class__.__name__)
            
            if parent is not None:
                if not isinstance(parent, (Model, Key)):
                    raise TypeError('Expected Model type; received %s (is %s)' %
                                    (parent, parent.__class__.__name__))
                if isinstance(parent, Model) and not parent.has_key():
                    raise BadValueError(
                        "%s instance must have a complete key before it can be used as a "
                        "parent." % parent.kind())
                if isinstance(parent, Key):
                    self._parent_key = parent
                    self._parent = None
                else:
                    self._parent_key = parent.key()
                    self._parent = parent
            else:
                self._parent_key = None
                self._parent = None
            self._key_name = key_name
            self._key = None
        
        if self._parent_key is not None:
            if namespace is not None and self._parent_key.namespace() != namespace:
                raise BadArgumentError(
                    'Expected parent namespace to be %r; received %r' %
                    (namespace, self._parent_key.namespace()))
            namespace = self._parent_key.namespace()
        
        self._entity = None
        if _app is not None and isinstance(_app, Key):
            raise BadArgumentError('_app should be a string; received Key(\'%s\'):\n'
                                   '  This may be the result of passing \'key\' as '
                                   'a positional parameter in SDK 1.2.6.  Please '
                                   'only pass \'key\' as a keyword parameter.' % _app)
        if namespace is None:
            namespace = namespace_manager.get_namespace()
    
        self._app = _app
        self.__namespace = namespace
    
        for prop in self.properties().values():
            if prop.name in kwds:
                value = kwds[prop.name]
            else:
                value = prop.default_value()
            if value is None:
                continue
            try:
                prop.__set__(self, value)
            except DerivedPropertyError, e:
                if prop.name in kwds and not _from_entity:
                    raise

    def put(self, **kwargs):
        if not self.is_saved():
            self._key_name = self._generate_key()
        super(Model, self).put(**kwargs)

    @classmethod
    def kind(cls):
        '''Return class name in format <AppnameClassname>'''
        # get application name from path "apps/app_name/models"
        app_name = cls.__module__.split('.')[1].title()
        # application name and model name have the same name like BlogBlog
        if app_name == cls.__name__:
            return cls.__name__
        # add application name as prefix of class
        return "%s%s" % (app_name, cls.__name__)

    def _generate_key(self):
        """A Model for entities which automatically generate their own key names
        on creation."""
        key_name = None
        if hasattr(self, 'KEY_NAME'):
            # get value for each property
            properties = dict()
            for prop in self.properties().values():
                value = getattr(self, prop.name)
                if hasattr(value, 'key'):
                    value = value.key().name()
                properties[prop.name] = value 
            # The KEY_NAME must either be a callable or a string.
            # If it's a callable,we call it with the given keyword args.
            if callable(self.KEY_NAME):
                key_name = self.KEY_NAME(properties)
            # If it's a string, we just interpolate the keyword args into the
            # string, ensuring that this results in a different string.
            else:
                # Try to create the key name, catching any key errors arising
                # from the string interpolation
                try:
                    key_name = str(self.KEY_NAME) % properties
                except KeyError:
                    raise RuntimeError, "Missing keys required by %s entity's "\
                                        "KEY_NAME property (got %r)" %\
                                        (self.entity_type(), properties)
                # Make sure the generated key name is actually different from
                # the template
                if key_name == str(self.KEY_NAME):
                    raise RuntimeError, 'Key name generated for %s entity is '\
                                        'same as KEY_NAME template' %\
                                        self.entity_type()
        # return changed arguments
        return key_name


class Property(Property):
    __metaclass__ = monkey_patch

    def validate(self, value):
        '''
        Fixed: for attribute [String]ListProperty used choices parameter,
               Validation is incorrect, because compared list value with each
               value in choices.
        '''
        if self.empty(value):
            if self.required:
                raise BadValueError('Property %s is required' % self.name)
        else:
            if self.choices:
                import types
                match = False
                if type(value) in (types.ListType, types.TupleType):
                    # delete duplicates
                    value = list(set(value))
                    # all values is in choices
                    if not list(set(value).difference(set(self.choices))):
                        match = True
                else:
                    # one value is is choices
                    if value in self.choices:
                        match = True
                if not match:
                    raise BadValueError('Property1 %s is %r; must be one of %r' %
                                           (self.name, value, self.choices))
        if self.validator is not None:
            self.validator(value)
        return value


class PhoneListProperty(StringListProperty):
    max_length = 10
    min_length = 5

    def validate(self, value):
        value = super(ListProperty, self).validate(value)
        if value is not None:
            # check each phone
            for item in value:
                if len(item) > self.max_length or len(item) < self.min_length:
                    raise BadValueError(
                        'Phone number %s is incorrect. Enter number in format (xxx) xx-xx-xxx' %
                        (item,))
        return value

    def get_form_field(self, **kwargs):
        defaults = {'initial': ""}
        defaults.update(kwargs)
        return super(StringListProperty, self).get_form_field(**defaults)

    def get_value_for_form(self, instance):
        value = super(PhoneListProperty, self).get_value_for_form(instance)
        if not value:
            value = "---"
        if isinstance(value, basestring) and len(value)>0:
            value = value.splitlines()
        if isinstance(value, list):
            value = ', '.join(value)
        return value

    def make_value_from_form(self, value):
        import re
        if not value:
            return []
        if isinstance(value, basestring):
            # delete all not numbers
            value = [re.sub('[^0-9]', '', phone) for phone in value.split(',')]
        return value

class ReferenceListProperty(ListProperty):
    def __init__(self, reference_to, verbose_name=None, default=None, **kwds):
        self.reference_to = reference_to
        super(ReferenceListProperty, self).__init__(item_type=Key,
                                                verbose_name=verbose_name,
                                                default=default,
                                                **kwds)

class ImageProperty(UnindexedProperty):
    """Image blob"""

    data_type  = Blob
    min_width  = None
    min_height = None
    max_width  = None
    max_height = None
    max_size   = None

    def __init__(self, verbose_name=None, min_width=None, min_height=None,
                 max_width=None, max_height=None, max_size=None, **kwds):
        """
        Construct image property.
    
        Args:
          verbose_name: Verbose name is always first parameter.
          min_width: minimal image width (in pixels).
          min_height: minimal image height (in pixels).
          max_width: maximum image width (in pixels).
          max_height: maximum image height (in pixels).
          max_size: maximum image file size (in kilobytes).
        """
        super(ImageProperty, self).__init__(verbose_name, **kwds)
        self.min_width  = min_width
        self.min_height = min_height
        self.max_width  = max_width
        self.max_height = max_height
        self.max_size   = max_size

    def validate(self, value):
        from google.appengine.api import images
        """Validate image property.
    
        Returns:
          A valid value.
    
        Raises:
          BadValueError if property is image incorrect size.
        """
        value = super(ImageProperty, self).validate(value)
        # value not defined
        if value is None:
            return value
        # check image size
        if self.min_width or self.min_height or self.max_width or self.max_height:
            image = images.Image(value)
            if (self.min_width is not None and image.width < self.min_width) or \
               (self.min_height is not None and image.height < self.min_height):
                raise BadValueError('Property %s has too small width or height' % self.name)
            if (self.max_width is not None and image.width > self.max_width) or \
               (self.max_height is not None and image.height > self.max_height):
                raise BadValueError('Property %s has too large width or height' % self.name)
        # check image file size
        if self.max_size and len(value) > self.max_size * 1024:
            raise BadValueError('Property %s has too large content' % self.name)
        return value

class UserProperty(Property):
    """A user property."""

    def __init__(self,
                 auto_current_user=False,
                 auto_current_user_add=False,
                 **kwargs):
        """Initializes this Property with the given options.
    
        Note: this does *not* support the 'default' keyword argument.
        Use auto_current_user_add=True instead.
    
        Args:
          verbose_name: User friendly name of property.
          name: Storage name for property.  By default, uses attribute name
            as it is assigned in the Model sub-class.
          required: Whether property is required.
          validator: User provided method used for validation.
          choices: User provided set of valid property values.
          auto_current_user: If true, the value is set to the current user
            each time the entity is written to the datastore.
          auto_current_user_add: If true, the value is set to the current user
            the first time the entity is written to the datastore.
          indexed: Whether property is indexed.
        """
        from apps.user.models import User
        super(UserProperty, self).__init__(**kwargs)
        self.auto_current_user = auto_current_user
        self.auto_current_user_add = auto_current_user_add
        self.data_type = User

    def validate(self, value):
        """Validate user.
    
        Returns:
          A valid value.
    
        Raises:
          BadValueError if property is not instance of 'User'.
        """
        value = super(UserProperty, self).validate(value)
        if value is not None and not isinstance(value, self.data_type):
            raise BadValueError('Property %s must be a %s' % (self.name, self.data_type.__class__.__name__))
        return value

    def default_value(self):
        """Default value for user.
    
        Returns:
          Value of get_current_user() if auto_current_user or
          auto_current_user_add is set; else None. (But *not* the default
          implementation, since we don't support the 'default' keyword
          argument.)
        """
        from apps.user import get_current_user
        if self.auto_current_user or self.auto_current_user_add:
            return get_current_user()
        return None

    def get_value_for_datastore(self, model_instance):
        """Get value from property to send to datastore.
    
        Returns:
          Value of get_current_user() if auto_current_user is set;
          else the default implementation.
        """
        user = None
        from apps.user import get_current_user
        if self.auto_current_user:
            user = get_current_user()
        else:
            user = super(UserProperty, self).get_value_for_datastore(model_instance)
        # return user representation for save as Key
        return user and user.key()

    def make_value_from_datastore(self, value):
        if value is None:
            return None
        # restore user object by key
        return self.data_type.get(value)

class StringProperty(StringProperty):
    """A textual property, which can be multi- or single-line."""

    def __init__(self, verbose_name=None, multiline=False, min_length=None, max_length=None, **kwds):
        """Construct string property.
        
        Args:
            verbose_name: Verbose name is always first parameter.
            multi-line: Carriage returns permitted in property.
            min_length: Minimal length of string.
            max_length: Maximal length of string.
        """
        super(StringProperty, self).__init__(verbose_name, **kwds)
        self.multiline = multiline
        self.min_length = min_length
        self.max_length = max_length

    def validate(self, value):
        """Validate string property.
        
        Returns:
            A valid value.
        
        Raises:
            BadValueError if property is not multi-line but value is.
        """
        value = super(StringProperty, self).validate(value)
        if self.min_length and len(value) < self.min_length:
            raise BadValueError('Value is too short')
        if self.max_length and len(value) > self.max_length:
            raise BadValueError('Value is too long')
        return value

class SlugProperty(StringProperty):
    '''String starts with letter and contain only characters, numbers, underscores, dots and minus signs'''

    def validate(self, value):
        value = super(SlugProperty, self).validate(value)
        value = value.lower()
        if not re.match("^[a-z][a-z0-9._-]*$", value):
            raise BadValueError('Value %s should contain only letters, numbers, dots, underscore and minus sign' % self.name)
        return value